package com.ruoyi.knowledge.service.impl;

import com.ruoyi.chat.utils.ChatLangchainUtils;
import com.ruoyi.chat.utils.ChatLangchainUtils.FileSegmentResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.knowledge.constant.KnowledgeConstants;
import com.ruoyi.knowledge.domain.KnowledgeFile;
import com.ruoyi.knowledge.domain.KnowledgeFileSegment;
import com.ruoyi.knowledge.mapper.KnowledgeFileMapper;
import com.ruoyi.knowledge.service.IKnowledgeFileSegmentService;
import com.ruoyi.knowledge.service.IKnowledgeFileService;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 知识库文件Service业务层处理
 *
 * @author ruoyi
 */
@Service
public class KnowledgeFileServiceImpl implements IKnowledgeFileService {
    private static final Logger log = LoggerFactory.getLogger(KnowledgeFileServiceImpl.class);

    // 上传文件路径
    @Value("${file.upload.path:E:/ruoyi/uploadFile}")
    private String uploadPath;

    // Redis服务器地址
    @Value("${spring.data.redis.host}")
    private String redisHost;

    // Redis端口
    @Value("${spring.data.redis.port}")
    private int redisPort;


    @Autowired
    private KnowledgeFileMapper knowledgeFileMapper;

    @Autowired
    private ChatLangchainUtils chatLangchainUtils;

    @Autowired
    private IKnowledgeFileSegmentService knowledgeFileSegmentService;

    /**
     * 查询知识库文件
     *
     * @param id 知识库文件主键
     * @return 知识库文件
     */
    @Override
    public KnowledgeFile selectKnowledgeFileById(Long id) {
        return knowledgeFileMapper.selectKnowledgeFileById(id);
    }

    /**
     * 查询知识库文件列表
     *
     * @param knowledgeFile 知识库文件
     * @return 知识库文件
     */
    @Override
    public List<KnowledgeFile> selectKnowledgeFileList(KnowledgeFile knowledgeFile) {
        return knowledgeFileMapper.selectKnowledgeFileList(knowledgeFile);
    }

    /**
     * 根据知识库ID查询文件列表
     *
     * @param knowledgeId 知识库ID
     * @return 知识库文件集合
     */
    @Override
    public List<KnowledgeFile> selectKnowledgeFilesByKnowledgeId(Long knowledgeId) {
        return knowledgeFileMapper.selectKnowledgeFilesByKnowledgeId(knowledgeId);
    }

    /**
     * 新增知识库文件
     *
     * @param knowledgeFile 知识库文件
     * @return 结果
     */
    @Override
    public int insertKnowledgeFile(KnowledgeFile knowledgeFile) {
        return knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
    }

    /**
     * 修改知识库文件
     *
     * @param knowledgeFile 知识库文件
     * @return 结果
     */
    @Override
    public int updateKnowledgeFile(KnowledgeFile knowledgeFile) {
        return knowledgeFileMapper.updateKnowledgeFile(knowledgeFile);
    }

    /**
     * 批量删除知识库文件
     *
     * @param ids 需要删除的知识库文件主键
     * @return 结果
     */
    @Override
    public int deleteKnowledgeFileByIds(Long[] ids) {
        for (Long id : ids) {
            // 查询文件信息
            KnowledgeFile file = knowledgeFileMapper.selectKnowledgeFileById(id);
            if (file != null) {
                // 1. 清理Redis中的向量数据
                cleanupFileVectors(file);

                // 2. 删除物理文件
                if (StringUtils.isNotEmpty(file.getFileUrl())) {
                    FileUtils.deleteFile(file.getFileUrl());
                }
            }
        }
        return knowledgeFileMapper.deleteKnowledgeFileByIds(ids);
    }

    /**
     * 批量删除知识库文件
     *
     * @param ids 需要删除的知识库文件主键列表
     * @return 结果
     */
    @Override
    public int deleteKnowledgeFileByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return 0;
        }
        Long[] idArray = ids.toArray(new Long[0]);
        return deleteKnowledgeFileByIds(idArray);
    }

    /**
     * 删除知识库文件信息
     *
     * @param id 知识库文件主键
     * @return 结果
     */
    @Override
    public int deleteKnowledgeFileById(Long id) {
        // 查询文件信息
        KnowledgeFile file = knowledgeFileMapper.selectKnowledgeFileById(id);
        if (file != null) {
            // 1. 清理Redis中的向量数据
            cleanupFileVectors(file);

            // 2. 删除物理文件
            if (StringUtils.isNotEmpty(file.getFileUrl())) {
                FileUtils.deleteFile(file.getFileUrl());
            }
        }
        return knowledgeFileMapper.deleteKnowledgeFileById(id);
    }

    /**
     * 删除知识库下的所有文件
     *
     * @param knowledgeId 知识库ID
     * @return 结果
     */
    @Override
    public int deleteKnowledgeFilesByKnowledgeId(Long knowledgeId) {
        // 删除物理文件
        List<KnowledgeFile> files = knowledgeFileMapper.selectKnowledgeFilesByKnowledgeId(knowledgeId);
        for (KnowledgeFile file : files) {
            if (StringUtils.isNotEmpty(file.getFileUrl())) {
                FileUtils.deleteFile(file.getFileUrl());
            }
        }
        return knowledgeFileMapper.deleteKnowledgeFilesByKnowledgeId(knowledgeId);
    }

    /**
     * 上传知识库文件
     *
     * @param knowledgeId 知识库ID
     * @param file        文件
     * @param username    上传用户
     * @return 知识库文件信息
     */
    @Override
    @Transactional
    public KnowledgeFile uploadFile(Long knowledgeId, MultipartFile file, String username) throws Exception {
        if (file == null) {
            throw new Exception("上传文件不能为空");
        }

        // 判断文件类型
        String fileName = file.getOriginalFilename();
        String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        if (!checkFileExt(fileExt)) {
            throw new Exception("不支持的文件类型");
        }

        // 生成存储路径，根据日期创建目录
        String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
        String finalPath = uploadPath + "/" + datePath;

        // 创建目录
        File directory = new File(finalPath);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 生成唯一文件名
        //String uniqueFileName = System.currentTimeMillis() + "_" + fileName;
        String filePath = finalPath + "/" + fileName;

        // 保存文件
        try {
            File dest = new File(filePath);
            file.transferTo(dest);
            log.info("文件保存成功: {}", filePath);
        } catch (IOException e) {
            log.error("文件保存失败: {}", e.getMessage());
            throw new Exception("文件保存失败");
        }

        // 记录文件信息
        KnowledgeFile knowledgeFile = new KnowledgeFile();
        knowledgeFile.setKnowledgeId(knowledgeId);
        knowledgeFile.setFilename(fileName);
        knowledgeFile.setFileType(fileExt);
        knowledgeFile.setFileSize(file.getSize());
        knowledgeFile.setFileUrl(filePath);
        knowledgeFile.setUploader(username);
        // 未处理
        knowledgeFile.setFileStatus(KnowledgeConstants.VECTOR_STATUS_UNPROCESSED);
        knowledgeFile.setVectorCount(0);
        knowledgeFile.setCreateTime(new Date());
        knowledgeFile.setUpdateTime(new Date());

        int res = knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
        log.info("记录文件信息成功: {}", res);

        log.info("异步开始处理文件解析和向量化");
        // 异步开始处理文件向量化
        if (res == 1) {
            CompletableFuture.runAsync(() -> {
                processFile(knowledgeFile.getId());
            });
        }
        return knowledgeFile;
    }

    /**
     * 开始处理文件向量化
     *
     * @param knowledgeFileId 知识库文件ID
     * @return 结果
     */
    @Override
    public int processFile(Long knowledgeFileId) {
        KnowledgeFile knowledgeFile = knowledgeFileMapper.selectKnowledgeFileById(knowledgeFileId);
        log.info("开始处理文件向量化: 文件ID {}", knowledgeFileId);
        if (knowledgeFile == null) {
            log.error("向量化处理失败: 文件ID {} 不存在", knowledgeFileId);
            return 0;
        }

        log.info("开始处理文件 [ID:{}] [文件名:{}] 的向量化", knowledgeFileId, knowledgeFile.getFilename());

        // 更新为处理中状态
        KnowledgeFile updateFile = new KnowledgeFile();
        updateFile.setId(knowledgeFileId);
        // 处理中
        updateFile.setFileStatus(KnowledgeConstants.VECTOR_STATUS_PROCESSING);
        updateFile.setUpdateTime(new Date());
        int rows = knowledgeFileMapper.updateKnowledgeFile(updateFile);

        if (rows <= 0) {
            log.error("更新文件ID {} 为处理中状态失败", knowledgeFileId);
            return 0;
        }

        log.info("文件 [ID:{}] 状态已更新为处理中", knowledgeFileId);

        try {
            // 检查文件是否存在
            String filePath = knowledgeFile.getFileUrl();
            File file = new File(filePath);
            if (!file.exists()) {
                String errorMsg = "文件不存在: " + filePath;
                log.error(errorMsg);
                // 更新为处理失败状态
                updateFailedStatus(knowledgeFileId, errorMsg);
                return 0;
            }

            log.info("开始解析文件 [ID:{}] [类型:{}] [大小:{}]", knowledgeFileId, knowledgeFile.getFileType(),
                    knowledgeFile.getFileSize());

            // 使用Langchain4j进行文档处理和向量化
            // 创建唯一的命名空间，以知识库ID作为前缀
            String namespace = KnowledgeConstants.VECTOR_NAMESPACE_PREFIX + knowledgeFile.getKnowledgeId();
            log.info("使用命名空间: {}", namespace);

            // 获取文件类型
            String fileType = knowledgeFile.getFileType();

            // 执行文档向量化
            log.info("开始向量化处理文件 [ID:{}]", knowledgeFileId);
            long startTime = System.currentTimeMillis();

            // 使用新的方法获取切片数据
            log.info("切片文件：{}", file.getName());
            FileSegmentResult result = chatLangchainUtils.ingestFileWithSegments(file, fileType, namespace,
                    knowledgeFileId);
            int vectorCount = result.getVectorCount();

            long endTime = System.currentTimeMillis();

            log.info("文件 [ID:{}] 向量化处理完成, 耗时: {}ms, 生成向量数量: {}",
                    knowledgeFileId, (endTime - startTime), vectorCount);


            // 保存切片数据
            if (result.getSegments() != null && !result.getSegments().isEmpty()) {
                log.info("开始保存切片数据, 数量: {}", result.getSegments().size());

                // 将FileSegmentInfo转换为KnowledgeFileSegment
                List<KnowledgeFileSegment> segments = result.getSegments().stream().map(info -> {
                    KnowledgeFileSegment segment = new KnowledgeFileSegment();
                    segment.setFileId(info.getFileId());
                    segment.setContent(info.getContent());
                    segment.setSegmentIndex(info.getSegmentIndex());
                    segment.setVectorId(info.getVectorId());
                    return segment;
                }).collect(Collectors.toList());

                try {
                    // 先删除原有的切片数据
                    knowledgeFileSegmentService.deleteKnowledgeFileSegmentsByFileId(knowledgeFileId);

                    // 批量保存新的切片数据
                    int saveCount = knowledgeFileSegmentService.batchSaveKnowledgeFileSegments(segments);
                    log.info("切片数据保存成功, 数量: {}", saveCount);
                } catch (Exception e) {
                    log.error("保存切片数据失败: {}", e.getMessage(), e);
                    // 切片保存失败不影响向量化状态更新
                }
            } else {
                log.warn("没有切片数据需要保存");
            }

            // 更新为处理完成
            updateFile = new KnowledgeFile();
            updateFile.setId(knowledgeFileId);
            updateFile.setFileStatus(KnowledgeConstants.VECTOR_STATUS_COMPLETED); // 已完成
            updateFile.setVectorCount(vectorCount);
            updateFile.setUpdateTime(new Date());
            int result1 = knowledgeFileMapper.updateKnowledgeFile(updateFile);

            log.info("文件 [ID:{}] 状态已更新为已完成, 向量数量: {}", knowledgeFileId, vectorCount);

            return result1;
        } catch (Exception e) {
            log.error("文件 [ID:{}] 向量化处理失败: {}", knowledgeFileId, e.getMessage(), e);
            // 更新为失败状态
            updateFailedStatus(knowledgeFileId, e.getMessage());
            return 0;
        }
    }

    /**
     * 更新文件为处理失败状态
     *
     * @param knowledgeFileId 知识库文件ID
     * @param failReason      失败原因
     * @return 更新结果
     */
    private int updateFailedStatus(Long knowledgeFileId, String failReason) {
        KnowledgeFile updateFile = new KnowledgeFile();
        updateFile.setId(knowledgeFileId);
        updateFile.setFileStatus(KnowledgeConstants.VECTOR_STATUS_FAILED); // 失败
        updateFile.setFailReason(failReason);
        updateFile.setUpdateTime(new Date());
        int result = knowledgeFileMapper.updateKnowledgeFile(updateFile);
        log.info("文件 [ID:{}] 状态已更新为处理失败, 数据库更新结果: {}", knowledgeFileId, result > 0 ? "成功" : "失败");
        return result;
    }

    /**
     * 检查文件类型是否支持
     */
    private boolean checkFileExt(String fileExt) {
        if (StringUtils.isEmpty(fileExt)) {
            return false;
        }

        // 支持的文件类型：文本、PDF、Word等
        String[] allowExt = MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION;
        for (String ext : allowExt) {
            if (ext.equalsIgnoreCase(fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 清理文件相关的Redis向量数据
     *
     * @param file 知识库文件
     */
    private void cleanupFileVectors(KnowledgeFile file) {
        try {
            // 1. 查询该文件的所有切片
            List<KnowledgeFileSegment> segments =
                    knowledgeFileSegmentService.selectKnowledgeFileSegmentsByFileId(file.getId());
            if (segments == null || segments.isEmpty()) {
                log.info("文件 [ID:{}] 没有关联的切片数据", file.getId());
                return;
            }

            log.info("开始清理文件 [ID:{}] 的向量数据, 共 {} 条切片", file.getId(), segments.size());

            // 2. 准备从Redis中删除向量
            String namespace = KnowledgeConstants.VECTOR_NAMESPACE_PREFIX + file.getKnowledgeId();

            // 3. 尝试逐个删除向量
            for (KnowledgeFileSegment segment : segments) {
                if (segment.getVectorId() != null && !segment.getVectorId().isEmpty()) {
                    try {
                        // 从Redis向量库中删除向量
                        // 由于Langchain4j库的限制，当前可能无法直接通过ID删除向量
                        // 这里先尝试调用通用方法，后续可以直接操作Redis实现
                        log.info("从向量库删除向量, 命名空间: {}, 向量ID: {}", namespace, segment.getVectorId());

                        // 获取向量库
                        EmbeddingStore<TextSegment> embeddingStore =
                                chatLangchainUtils.getLocalEmbeddedStore(namespace);

                        // 这里尝试删除向量，但当前langchain4j可能不支持直接删除
                        // 此处留作未来扩展点
                    } catch (Exception e) {
                        log.error("删除向量失败, 向量ID: {}, 错误: {}", segment.getVectorId(), e.getMessage());
                        // 继续处理下一个，不影响主流程
                    }
                }
            }

            // 4. 使用file ID批量删除文件的所有切片数据
            knowledgeFileSegmentService.deleteKnowledgeFileSegmentsByFileId(file.getId());
            log.info("文件 [ID:{}] 的切片数据和向量数据清理完成", file.getId());
        } catch (Exception e) {
            log.error("清理文件向量数据失败, 文件ID: {}, 错误: {}", file.getId(), e.getMessage());
            // 不抛出异常，继续后续流程
        }
    }
}
